/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.helper;

import org.ohosannotations.ElementValidation;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

/**
 * 验证器参数辅助
 *
 * @author dev
 * @since 2021-07-22
 */
public class ValidatorParameterHelper {
    /**
     * Validator
     *
     * @since 2021-07-22
     */
    public interface Validator {
        /**
         * validate
         *
         * @param executableElement 元素
         * @param validation 元素验证
         */
        void validate(ExecutableElement executableElement, ElementValidation validation);
    }

    /**
     * ParameterRequirement
     *
     * @since 2021-07-22
     */
    public interface ParameterRequirement {
        /**
         * setMultiple
         */
        void setMultiple();

        /**
         * multiple
         *
         * @return boolean 布尔值
         */
        boolean multiple();

        /**
         * setOptional
         */
        void setOptional();

        /**
         * required
         *
         * @return boolean布尔值
         */
        boolean required();

        /**
         * isSatisfied
         *
         * @param parameter 验证元素
         * @return boolean布尔值
         */
        boolean isSatisfied(VariableElement parameter);
    }

    /**
     * NoParamValidator
     *
     * @since 2021-07-22
     */
    public static class NoParamValidator implements Validator {
        @Override
        public void validate(ExecutableElement executableElement, ElementValidation validation) {
            if (!executableElement.getParameters().isEmpty()) {
                validation.addError("%s cannot have any parameters");
            }
        }
    }

    /**
     * OneParamValidator
     *
     * @since 2021-07-22
     */
    public static class OneParamValidator implements Validator {
        private ParameterRequirement parameterRequirement;

        /**
         * 构造参数
         *
         * @param param 参数要求
         */
        public OneParamValidator(ParameterRequirement param) {
            parameterRequirement = param;
        }

        /**
         * optional
         *
         * @return OneParamValidator
         */
        public OneParamValidator optional() {
            parameterRequirement.setOptional();
            return this;
        }

        /**
         * multiple
         *
         * @return this
         */
        public OneParamValidator multiple() {
            parameterRequirement.setMultiple();
            return this;
        }

        /**
         * 验证
         *
         * @param executableElement 执行原理
         * @param validation 确认
         */
        @Override
        public void validate(ExecutableElement executableElement, ElementValidation validation) {
            List<? extends VariableElement> parameters = executableElement.getParameters();
            if (!parameterRequirement.multiple()) {
                if (parameterRequirement.required() && parameters.size() != 1) {
                    invalidate(validation);
                    return;
                }
                if (!parameterRequirement.required() && parameters.size() > 1) {
                    invalidate(validation);
                    return;
                }
            }

            for (VariableElement parameter : parameters) {
                if (!parameterRequirement.isSatisfied(parameter)) {
                    invalidate(validation);
                    return;
                }
            }
        }

        /**
         * invalidate
         *
         * @param validation 确认
         */
        protected void invalidate(ElementValidation validation) {
            validation.addError("method annotated with %s can only" +
                " have the following parameter: " + parameterRequirement);
        }
    }

    /**
     * BaseParamValidator
     *
     * @param <V> 子类泛型
     * @since 2021-07-22
     */
    private abstract class BaseParamValidator<V extends BaseParamValidator<?>> implements Validator {
        private List<ParameterRequirement> parameterRequirements = new ArrayList<>();
        private List<ParameterRequirement> originalParameterRequirements;

        /**
         * validate
         *
         * @param executableElement executableElement
         * @param validation validation
         */
        @Override
        public void validate(ExecutableElement executableElement, ElementValidation validation) {
            originalParameterRequirements = new ArrayList<>(parameterRequirements);
        }

        /**
         * type
         *
         * @param qualifiedName qualifiedName
         * @return param
         */
        public V type(String qualifiedName) {
            return param(new ExactTypeParameterRequirement(qualifiedName));
        }

        /**
         * extendsType
         *
         * @param qualifiedName qualifiedName
         * @return param
         */
        public V extendsType(String qualifiedName) {
            return param(new ExtendsTypeParameterRequirement(qualifiedName));
        }

        /**
         * extendsAnyOfTypes
         *
         * @param types types
         * @return param
         */
        public V extendsAnyOfTypes(String... types) {
            return param(new ExtendsAnyOfTypesParameterRequirement(types));
        }

        /**
         * anyType
         *
         * @return param
         */
        public V anyType() {
            return param(new AnyTypeParameterRequirement());
        }

        /**
         * annotatedWith
         *
         * @param annotationClass annotationClass
         * @return param
         */
        public V annotatedWith(Class<? extends Annotation> annotationClass) {
            return param(new AnnotatedWithParameterRequirement(annotationClass));
        }

        /**
         * primitiveOrWrapper
         *
         * @param primitive primitive
         * @return param
         */
        public V primitiveOrWrapper(TypeKind primitive) {
            return param(new PrimitiveOrWrapperParameterRequirement(primitive));
        }

        /**
         * anyOfTypes
         *
         * @param types types
         * @return param
         */
        public V anyOfTypes(String... types) {
            return param(new AnyOfTypesParameterRequirement(types));
        }

        /**
         * param
         *
         * @param parameterRequirement parameterRequirement
         * @return castThis
         */
        public V param(ParameterRequirement parameterRequirement) {
            parameterRequirements.add(parameterRequirement);
            return castThis();
        }

        /**
         * optional
         *
         * @return castThis
         */
        public V optional() {
            lastParam().setOptional();
            return castThis();
        }

        /**
         * multiple
         *
         * @return castThis
         */
        public V multiple() {
            lastParam().setMultiple();
            return castThis();
        }

        /**
         * getParamRequirements
         *
         * @return parameterRequirements
         */
        protected List<ParameterRequirement> getParamRequirements() {
            return parameterRequirements;
        }

        /**
         * lastParam
         *
         * @return parameterRequirements
         */
        private ParameterRequirement lastParam() {
            if (parameterRequirements.isEmpty()) {
                throw new IllegalStateException("Call type, extendsType, annotatedWith or param before");
            }
            return parameterRequirements.get(parameterRequirements.size() - 1);
        }

        /**
         * invalidate
         *
         * @param element element
         * @param validation validation
         */
        protected void invalidate(ExecutableElement element, ElementValidation validation) {
            validation.addError("%s can only have the following parameters: " + createMessage(element));
        }

        /**
         * createMessage
         *
         * @param element element
         * @return builder
         */
        protected String createMessage(ExecutableElement element) {
            StringBuilder builder = new StringBuilder();
            builder.append("[ ");
            for (ParameterRequirement parameterRequirement : originalParameterRequirements) {
                builder.append(parameterRequirement).append(", ");
            }
            return builder.append(" ]").toString();
        }

        /**
         * castThis
         *
         * @return this
         */
        @SuppressWarnings("unchecked")
        private V castThis() {
            return (V) this;
        }
    }

    /**
     * BaseParamValidator
     *
     * @since 2021-07-22
     */
    public class InOrderParamValidator extends BaseParamValidator<InOrderParamValidator> {
        private int index = -1;
        private ParameterRequirement currentParameterRequirement;
        private List<ParameterRequirement> satisfiedParameterRequirements = new ArrayList<>();

        private void nextParameterRequirement() {
            index++;
            if (index < getParamRequirements().size()) {
                currentParameterRequirement = getParamRequirements().get(index);
            } else {
                currentParameterRequirement = null;
            }
        }

        /**
         * validate
         *
         * @param executableElement executableElement
         * @param validation validation
         */
        @Override
        public void validate(ExecutableElement executableElement, ElementValidation validation) {
            super.validate(executableElement, validation);

            nextParameterRequirement();
            for (VariableElement parameter : executableElement.getParameters()) {
                if (!validate(parameter)) {
                    invalidate(executableElement, validation);
                    return;
                }
            }

            for (ParameterRequirement expectedParameter : getParamRequirements()) {
                if (expectedParameter.required() && !satisfiedParameterRequirements.contains(expectedParameter)) {
                    invalidate(executableElement, validation);
                    return;
                }
            }
        }

        /**
         * validate
         *
         * @param parameter parameter
         * @return true
         */
        private boolean validate(VariableElement parameter) {
            if (currentParameterRequirement == null) {
                return false;
            }
            if (currentParameterRequirement.isSatisfied(parameter)) {
                satisfiedParameterRequirements.add(currentParameterRequirement);
                if (!currentParameterRequirement.multiple()) {
                    nextParameterRequirement();
                }
            } else {
                if (currentParameterRequirement.required()
                    && !satisfiedParameterRequirements.contains(currentParameterRequirement)) {
                    return false;
                } else {
                    nextParameterRequirement();
                    return validate(parameter);
                }
            }
            return true;
        }

        /**
         * createMessage
         *
         * @param element element
         * @return super
         */
        @Override
        protected String createMessage(ExecutableElement element) {
            return super.createMessage(element) + " in the order above";
        }
    }

    /**
     * BaseParamValidator
     *
     * @since 2021-07-22
     */
    public class AnyOrderParamValidator extends BaseParamValidator<AnyOrderParamValidator> {
        private List<ParameterRequirement> satisfiedParameterRequirements = new ArrayList<>();

        /**
         * validate
         *
         * @param executableElement executableElement
         * @param validation validation
         */
        @Override
        public void validate(ExecutableElement executableElement, ElementValidation validation) {
            super.validate(executableElement, validation);

            for (VariableElement parameter : executableElement.getParameters()) {
                ParameterRequirement foundParameter = null;

                for (ParameterRequirement expectedParameter : getParamRequirements()) {
                    if (expectedParameter.isSatisfied(parameter)) {
                        satisfiedParameterRequirements.add(expectedParameter);
                        foundParameter = expectedParameter;
                        break;
                    }
                }

                if (foundParameter == null) {
                    invalidate(executableElement, validation);
                    return;
                }

                if (!foundParameter.multiple()) {
                    getParamRequirements().remove(foundParameter);
                }
            }

            for (ParameterRequirement expectedParameter : getParamRequirements()) {
                if (expectedParameter.required() && !satisfiedParameterRequirements.contains(expectedParameter)) {
                    invalidate(executableElement, validation);
                    return;
                }
            }
        }

        /**
         * createMessage
         *
         * @param element element
         * @return super
         */
        @Override
        protected String createMessage(ExecutableElement element) {
            return super.createMessage(element) + " in any order";
        }
    }

    /**
     * BaseParameterRequirement
     *
     * @since 2021-07-22
     */
    public abstract class BaseParameterRequirement implements ParameterRequirement {
        private boolean required = true;
        private boolean multiple = false;

        /**
         * setMultiple
         */
        @Override
        public void setMultiple() {
            multiple = true;
        }

        /**
         * multiple
         *
         * @return multiple
         */
        @Override
        public boolean multiple() {
            return multiple;
        }

        /**
         * setOptional
         */
        @Override
        public void setOptional() {
            required = false;
        }

        /**
         * required
         *
         * @return required
         */
        @Override
        public boolean required() {
            return required;
        }

        /**
         * description
         *
         * @return String 字符串
         */
        protected abstract String description();

        /**
         * toString
         *
         * @return String
         */
        @Override
        public String toString() {
            return String.format("[ %s %s%s]", description(),
                required ? "" : "(optional) ", multiple ? "(multiple) " : "");
        }
    }

    /**
     * ExactTypeParameterRequirement
     *
     * @since 2021-07-22
     */
    public class ExactTypeParameterRequirement extends BaseParameterRequirement {
        private String typeName;

        /**
         * ExactTypeParameterRequirement
         *
         * @param typeName typeName
         */
        public ExactTypeParameterRequirement(String typeName) {
            this.typeName = typeName;
        }

        /**
         * isSatisfied
         *
         * @param param param
         * @return param
         */
        @Override
        public boolean isSatisfied(VariableElement param) {
            return param.asType().toString().equals(typeName);
        }

        /**
         * description
         *
         * @return typeName
         */
        @Override
        protected String description() {
            return typeName;
        }
    }

    /**
     * ExtendsTypeParameterRequirement
     *
     * @since 2021-07-22
     */
    public class ExtendsTypeParameterRequirement extends BaseParameterRequirement {
        private String typeName;

        /**
         * ExtendsTypeParameterRequirement
         *
         * @param typeName typeName
         */
        public ExtendsTypeParameterRequirement(String typeName) {
            this.typeName = typeName;
        }

        /**
         * isSatisfied
         *
         * @param param param
         * @return false
         */
        @Override
        public boolean isSatisfied(VariableElement param) {
            TypeMirror elementType = param.asType();
            TypeElement typeElement = annotationHelper.typeElementFromQualifiedName(typeName);
            if (typeElement != null) {
                TypeMirror expectedType = typeElement.asType();
                return annotationHelper.isSubtype(elementType, expectedType);
            }
            return false;
        }

        /**
         * description
         *
         * @return extending
         */
        @Override
        protected String description() {
            return "extending " + typeName;
        }
    }

    /**
     * ExtendsAnyOfTypesParameterRequirement
     *
     * @since 2021-07-22
     */
    public class ExtendsAnyOfTypesParameterRequirement extends BaseParameterRequirement {
        private List<String> types;

        /**
         * ExtendsAnyOfTypesParameterRequirement
         *
         * @param types types
         */
        public ExtendsAnyOfTypesParameterRequirement(String... types) {
            this.types = Arrays.asList(types);
        }

        /**
         * isSatisfied
         *
         * @param parameter parameter
         * @return false
         */
        @Override
        public boolean isSatisfied(VariableElement parameter) {
            TypeMirror elementType = parameter.asType();

            for (String type : types) {
                TypeElement typeElement = annotationHelper.typeElementFromQualifiedName(type);
                if (typeElement != null) {
                    TypeMirror expectedType = typeElement.asType();
                    if (annotationHelper.isSubtype(elementType, expectedType)) {
                        return true;
                    }
                }
            }
            return false;
        }

        /**
         * description
         *
         * @return builder
         */
        @Override
        protected String description() {
            StringBuilder builder = new StringBuilder();
            builder.append("extending one of the following: [");
            for (int i = 0; i < types.size() - 1; ++i) {
                builder.append(types.get(i)).append(", ");
            }
            builder.append(types.get(types.size() - 1)).append(" ]");

            return builder.toString();
        }
    }

    /**
     * AnnotatedWithParameterRequirement
     *
     * @since 2021-07-22
     */
    public class AnnotatedWithParameterRequirement extends BaseParameterRequirement {
        private Class<? extends Annotation> annotationClass;

        /**
         * AnnotatedWithParameterRequirement
         *
         * @param annotationClass annotationClass
         */
        public AnnotatedWithParameterRequirement(Class<? extends Annotation> annotationClass) {
            this.annotationClass = annotationClass;
        }

        /**
         * isSatisfied
         *
         * @param param param
         * @return param
         */
        @Override
        public boolean isSatisfied(VariableElement param) {
            return param.getAnnotation(annotationClass) != null;
        }

        /**
         * description
         *
         * @return annotated
         */
        @Override
        protected String description() {
            return "annotated with " + annotationClass.getSimpleName();
        }
    }

    /**
     * PrimitiveOrWrapperParameterRequirement
     *
     * @since 2021-07-22
     */
    public class PrimitiveOrWrapperParameterRequirement extends BaseParameterRequirement {
        private TypeKind type;

        /**
         * PrimitiveOrWrapperParameterRequirement
         *
         * @param type type
         */
        public PrimitiveOrWrapperParameterRequirement(TypeKind type) {
            this.type = type;
        }

        /**
         * description
         *
         * @return type
         */
        @Override
        protected String description() {
            return type.toString().toLowerCase() + " or " + Locale.ROOT + getWrapperType();
        }

        /**
         * isSatisfied
         *
         * @param parameter parameter
         * @return parameter
         */
        @Override
        public boolean isSatisfied(VariableElement parameter) {
            return parameter.asType().getKind() == type || parameter.asType().toString().equals(getWrapperType());
        }

        /**
         * getWrapperType
         *
         * @return CanonicalNameConstants
         */
        private String getWrapperType() {
            switch (type) {
                case BOOLEAN:
                    return CanonicalNameConstants.BOOLEAN;
                case INT:
                    return CanonicalNameConstants.INTEGER;
                case BYTE:
                    return CanonicalNameConstants.BYTE;
                case SHORT:
                    return CanonicalNameConstants.SHORT;
                case LONG:
                    return CanonicalNameConstants.LONG;
                case CHAR:
                    return CanonicalNameConstants.CHAR;
                case FLOAT:
                    return CanonicalNameConstants.FLOAT;
                case DOUBLE:
                    return CanonicalNameConstants.DOUBLE;
                default:
                    throw new IllegalArgumentException("The TypeKind passed does not represent a primitive");
            }
        }
    }

    /**
     * AnyOfTypesParameterRequirement
     *
     * @since 2021-07-22
     */
    public class AnyOfTypesParameterRequirement extends BaseParameterRequirement {
        private List<String> types;

        /**
         * AnyOfTypesParameterRequirement
         *
         * @param types types
         */
        public AnyOfTypesParameterRequirement(String... types) {
            this.types = Arrays.asList(types);
        }

        /**
         * isSatisfied
         *
         * @param parameter parameter
         * @return types
         */
        @Override
        public boolean isSatisfied(VariableElement parameter) {
            return types.contains(parameter.asType().toString());
        }

        /**
         * description
         *
         * @return Arrays
         */
        @Override
        protected String description() {
            return Arrays.toString(types.toArray());
        }
    }

    /**
     * AnyTypeParameterRequirement
     *
     * @since 2021-07-22
     */
    public class AnyTypeParameterRequirement extends BaseParameterRequirement {
        /**
         * isSatisfied
         *
         * @param parameter parameter
         * @return true
         */
        @Override
        public boolean isSatisfied(VariableElement parameter) {
            return true;
        }

        /**
         * description
         *
         * @return any
         */
        @Override
        protected String description() {
            return "any type";
        }
    }

    /**
     * 没有参数
     * noParam
     *
     * @return NoParamValidator
     */
    public Validator noParam() {
        return new NoParamValidator();
    }

    /**
     * 类型
     * type
     *
     * @param qualifiedName qualifiedName
     * @return param
     */
    public OneParamValidator type(String qualifiedName) {
        return param(new ExactTypeParameterRequirement(qualifiedName));
    }

    /**
     * 扩展类型
     * extendsType
     *
     * @param qualifiedName qualifiedName
     * @return param
     */
    public OneParamValidator extendsType(String qualifiedName) {
        return param(new ExtendsTypeParameterRequirement(qualifiedName));
    }

    /**
     * 扩展的类型
     * extendsAnyOfTypes
     *
     * @param types types
     * @return param
     */
    public OneParamValidator extendsAnyOfTypes(String... types) {
        return param(new ExtendsAnyOfTypesParameterRequirement(types));
    }

    /**
     * 任何类型
     * anyType
     *
     * @return param
     */
    public OneParamValidator anyType() {
        return param(new AnyTypeParameterRequirement());
    }

    /**
     * 注释与
     * annotatedWith
     *
     * @param annotationClass annotationClass
     * @return param
     */
    public OneParamValidator annotatedWith(Class<? extends Annotation> annotationClass) {
        return param(new AnnotatedWithParameterRequirement(annotationClass));
    }

    /**
     * 原始的或包装
     * primitiveOrWrapper
     *
     * @param primitive primitive
     * @return param
     */
    public OneParamValidator primitiveOrWrapper(TypeKind primitive) {
        return param(new PrimitiveOrWrapperParameterRequirement(primitive));
    }

    /**
     * 任何类型的
     * anyOfTypes
     *
     * @param types types
     * @return param
     */
    public OneParamValidator anyOfTypes(String... types) {
        return param(new AnyOfTypesParameterRequirement(types));
    }

    /**
     * 参数
     * param
     *
     * @param parameterRequirement parameterRequirement
     * @return OneParamValidator
     */
    public OneParamValidator param(ParameterRequirement parameterRequirement) {
        return new OneParamValidator(parameterRequirement);
    }

    /**
     * 为了
     * inOrder
     *
     * @return InOrderParamValidator
     */
    public InOrderParamValidator inOrder() {
        return new InOrderParamValidator();
    }

    /**
     * 任何顺序
     * anyOrder
     *
     * @return AnyOrderParamValidator
     */
    public AnyOrderParamValidator anyOrder() {
        return new AnyOrderParamValidator();
    }

    /**
     * 注释的助手
     */
    protected final TargetAnnotationHelper annotationHelper;

    /**
     * 验证器参数辅助
     * ValidatorParameterHelper
     *
     * @param targetAnnotationHelper targetAnnotationHelper
     */
    public ValidatorParameterHelper(TargetAnnotationHelper targetAnnotationHelper) {
        annotationHelper = targetAnnotationHelper;
    }
}
